
--[[
=head1 NAME

applets.JRMC - "Take me to The River"

=head1 DESCRIPTION

Access J River Media Center Library, playback via current play or the currently selcted J River Playback zone

=head1 FUNCTIONS


=cut
--]]

-- stuff we use
local setmetatable, tonumber, tostring, package, require, pairs, type, unpack = setmetatable, tonumber, tostring, package, require, pairs, type, unpack
local ipairs, string = ipairs, string


local io                     = require("io")
local oo                     = require("loop.simple")
local math                   = require("math")
local string                 = require("string")
local table                  = require("jive.utils.table")

local Player                 = require("jive.slim.Player")
local SlimServer             = require("jive.slim.SlimServer")
local SlimServers            = require("jive.slim.SlimServers")
local AppletManager = require("jive.AppletManager")


local Applet                 = require("jive.Applet")
local Framework              = require("jive.ui.Framework")
local Choice              = require("jive.ui.Choice")
local Label                  = require("jive.ui.Label")
local Popup                  = require("jive.ui.Popup")
local SimpleMenu             = require("jive.ui.SimpleMenu")
local Menu                   = require("jive.ui.Menu")
local Window                 = require("jive.ui.Window")
local Surface                 = require("jive.ui.Surface")
local Icon                   = require("jive.ui.Icon")
local Tile                   = require("jive.ui.Tile")

local Timer                  = require("jive.ui.Timer")
local Task             = require("jive.ui.Task")
local SocketHttp       = require("jive.net.SocketHttp")
local RequestHttp      = require("jive.net.RequestHttp")
local jnt              = jnt
local appletManager = appletManager
local log                    = require("jive.utils.log").addCategory("jrmc", jive.utils.log.DEBUG)
local debug = require("jive.utils.debug")
local json             = require("json")

local BASE_URL = "http://192.168.1.103:18887/json/"
--local PORT = 80

local EVENT_SCROLL     = jive.ui.EVENT_SCROLL
local EVENT_KEY_PRESS  = jive.ui.EVENT_KEY_PRESS
local EVENT_KEY_DOWN   = jive.ui.EVENT_KEY_DOWN
local EVENT_KEY_HOLD   = jive.ui.EVENT_KEY_HOLD
local KEY_BACK         = jive.ui.KEY_BACK
local KEY_ADD         = jive.ui.KEY_ADD
local KEY_GO           = jive.ui.KEY_GO
local KEY_HOME          = jive.ui.KEY_HOME
local KEY_PAUSE 	= jive.ui.KEY_PAUSE
local KEY_PLAY	 	= jive.ui.KEY_PLAY
local KEY_VOLUME_UP    = jive.ui.KEY_VOLUME_UP
local KEY_VOLUME_DOWN  = jive.ui.KEY_VOLUME_DOWN
local KEY_REW = jive.ui.KEY_REW
local KEY_FWD = jive.ui.KEY_FWD
local EVENT_CONSUME    = jive.ui.EVENT_CONSUME
local FRAME_RATE       = jive.ui.FRAME_RATE
local LAYER_FRAME      = jive.ui.LAYER_FRAME
local LAYER_CONTENT    = jive.ui.LAYER_CONTENT



module(..., Framework.constants)
oo.class(_M, Applet)

function jrmc(self, menuItem)
	log:info("jrmc")

	--reload each time for ease of development
	package.loaded["applets.JRMC.JRMCOutputHandler"] = nil
	local JRMCOutputHandler    = require("applets.JRMC.JRMCOutputHandler")

	package.loaded["applets.JRMC.SqueezeOutputHandler"] = nil
	local SqueezeOutputHandler    = require("applets.JRMC.SqueezeOutputHandler")

	self.jrmcOutputHandler = JRMCOutputHandler(self)
	self.squeezeOutputHandler = SqueezeOutputHandler(self)

	jnt:subscribe(self)

	-- find a server: server for current player, else first server
	sd = AppletManager:getAppletInstance("SlimDiscovery")

	if sd then
		if sd:getCurrentPlayer() then
			self.player = sd:getCurrentPlayer()
			log:info("Accessing player: ", self.player)
			self.server = self.player:getSlimServer()
			log:info("Accessing server: ", self.server:getIpPort())
			playerActive = true;
		else
			log:info("no current player");
		end
	end

	return self:menuTop(menuItem)
end

function notify_playerCurrent(meta, player)
	self.player = player;
	if player ~= nil then
		log:info("Switching to player: ", self.player)
		self.server = self.player:getSlimServer()
		log:info("Switching to server: ", self.server:getIpPort())
	end
end

function menuTop(self, menuItem)
	local window = Window("window", menuItem.text )
	local menu = SimpleMenu("menu")

	if (self.player) then
		menu:addItem(
			{ text = "Output:Squeeze",
				sound = "WINDOWSHOW",
				callback = function(event, menuItem)
					self:enableSqueezeOutputHandler()
					menuItem.url = "?command=show&location=Audio"

					--menuItem.url = "view?location=Media+Library%7CAudio%7CKate%27s+List%7CAimee+Mann"
					--menuItem.url = "?command=show&location=Audio%7CRating%7C5"

					self:menuWindow(menuItem)
				end }
		)
	end
	
	menu:addItem(
		{ text = "Output:Home Theater",
			sound = "WINDOWSHOW",
			callback = function(event, menuItem)
				self:enableJrmcOutputHandler()
				menuItem.url = ""
				--uncomment for quick testing
				--menuItem.url = "?command=show&location=Audio%7CKate%27s+List%7CAimee+Mann"
				self:menuWindow(menuItem)
			end }
	)
	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function enableJrmcOutputHandler(self)
	log:debug("Enabling JRMC Output" )
	self.currentOutputHandler = self.jrmcOutputHandler
end

function enableSqueezeOutputHandler(self)
	log:debug("Enabling Squeeze Output" )
	self.currentOutputHandler = self.squeezeOutputHandler
end

function menuWindow(self, menuItem)

	local window = Window("window", menuItem.text )
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)

	local menu = SimpleMenu("menu")
	self:jrmcRequest(menuItem.url, loadMenuItems, menu)

	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function jrmcRequest(self, url, callback, menu, outputHandlerCallback)
	log:info("url: ", url)
	fullurl = BASE_URL .. url
	log:info("fetching jrmc data from server: ", fullurl)
	local response = {}
	local req = RequestHttp(
		function(chunk, err)
			if err then
				log:error("error fetching jrmc data from server: " ..  fullurl)
			end
			if chunk then
				log:info("fetched jrmc data from server: ", fullurl)

				if (callback) then
					callback(self, json.decode(chunk), menu, outputHandlerCallback)
				end
			end
		end,
		'GET',
		fullurl
	)

	local uri  = req:getURI()
	local http = SocketHttp(jnt, uri.host, uri.port, uri.host)

	http:fetch(req)
end

function loadMenuItems(self, jrItems, menu)
	local items = {}
	for i, jrItem in ipairs(jrItems) do

		if (jrItem.isFolder) then
			jrItem.url = "?command=show&location=" .. jrItem.location
		else
		 	jrItem.url = "?command=play&file=" .. jrItem.file
		end
		jrItem.sound = "WINDOWSHOW"
		jrItem.callback = function(event, menuItem)
								if (menuItem.isFolder) then
									self:menuWindow(menuItem)
								else
									self:infoWindow(menuItem)
								end
							end
	end
  	menu:setItems(jrItems)
end

function loadPlayingNowItems(self, jrItems, menu)
	local items = {}
	local nowPlayingIndex = 1
	for i, jrItem in ipairs(jrItems) do
		jrItem.text = jrItem.name .. " - " .. jrItem.artist
		if (jrItem.isNowPlayingTrack) then
			nowPlayingIndex = i
			jrItem.icon  = Icon("nowPlaying")
		end
		jrItem.sound = "WINDOWSHOW"
		jrItem.callback = function(event, menuItem)
								self:infoWindow(menuItem, menuItem.name)
							end
	end
  	menu:setItems(jrItems)
  	menu:setSelectedIndex(nowPlayingIndex)
end

function infoWindow(self, menuItem, title)
	if not title then
		title = menuItem.text
	end

	local window = Window("window", title )
	--fallback to default handler
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD,
			   function(event)
			   	return keyEvent(self, event)
			   end)
	local menu = SimpleMenu("menu")
	self:jrmcRequest("?command=info&file=" .. menuItem.file, loadInfoItems, menu)

	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function nowPlayingWindow(self, menuItem)

	local window = Window("window", "Now Playing" )
	--fallback to default handler
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)
	window:addListener(EVENT_KEY_PRESS,
			   function(evt)
				   if (evt:getKeycode() == KEY_PLAY) then
				   		log:error('here')
				        self.currentOutputHandler:setNowPlayingIndex(self:getCurrentMenuSelectedItemIndex())

   					    --todo: remove timer, instead make this a callback after setNowPlayingIndex call completes, better yet just modify in place menu instead of reload
   					    local timer = Timer(500,
   					    function()
	   						window:hide()
							self:nowPlayingWindow()
						 end
			     		,true)
						timer:start()
				        return EVENT_CONSUME
				   end

				   return EVENT_UNUSED
			   end)


	local menu = SimpleMenu("menu")
	self:jrmcRequest("?command=now-playing", loadPlayingNowItems, menu)

	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function loadInfoItems(self, jrItems, menu)
	local items = {}
	for i, jrItem in ipairs(jrItems) do

		jrItem.text = jrItem.field .. ": " .. jrItem.formattedValue
		jrItem.sound = "WINDOWSHOW"
		jrItem.callback = function(event, menuItem)
							end
		jrItem.style = 'itemNoAction'
	end
  	menu:setItems(jrItems)
end

function keyEvent(self, evt)
  local key = evt:getKeycode()
  local eventType = evt:getType()
  log:debug("key:" .. key)

  if eventType == EVENT_KEY_PRESS or eventType == EVENT_KEY_DOWN or eventType == EVENT_KEY_HOLD then
  	if key == KEY_VOLUME_UP and eventType == EVENT_KEY_DOWN then
		if self.currentOutputHandler:volumeUp() then
			return EVENT_CONSUME
		end
	elseif key == KEY_VOLUME_DOWN  and eventType == EVENT_KEY_DOWN then
		if self.currentOutputHandler:volumeDown() then
			return EVENT_CONSUME
		end
	elseif key == KEY_PAUSE  and eventType == EVENT_KEY_PRESS then
		if self.currentOutputHandler:pause() then
			return EVENT_CONSUME
		end
	elseif key == KEY_PLAY  and eventType == EVENT_KEY_PRESS then
		self:showPlayPopup(self:getCurrentMenuSelectedItem())
		return EVENT_CONSUME
	elseif key == KEY_ADD  and eventType == EVENT_KEY_PRESS then
		self:showAddPopup(self:getCurrentMenuSelectedItem())
		return EVENT_CONSUME
	elseif key == KEY_PAUSE  and eventType == EVENT_KEY_HOLD then
		if self.currentOutputHandler:stop() then
			return EVENT_CONSUME
		end
--	elseif key == KEY_FWD  and (eventType == EVENT_KEY_DOWN or eventType == EVENT_KEY_PRESS) then
	elseif key == KEY_FWD  and (eventType == EVENT_KEY_DOWN) then
		return EVENT_CONSUME
	elseif key == KEY_FWD  and (eventType == EVENT_KEY_PRESS) then
		if self.currentOutputHandler:next() then
			return EVENT_CONSUME
		end
--	elseif key == KEY_REW  and (eventType == EVENT_KEY_DOWN or eventType == EVENT_KEY_PRESS) then
	elseif key == KEY_REW  and (eventType == EVENT_KEY_DOWN) then
		return EVENT_CONSUME
	elseif key == KEY_REW  and (eventType == EVENT_KEY_PRESS) then
		if self.currentOutputHandler:previous() then
			return EVENT_CONSUME
		end
	elseif key == KEY_HOME  and (eventType == EVENT_KEY_PRESS) then
		if self.currentOutputHandler:handleHomeKey() then
			self:showHomePopup()
			return EVENT_CONSUME
		end
	end
	return EVENT_UNUSED
  end
end

function getCurrentMenuSelectedItem(self)
	local menu = self:getCurrentMenu()
	return menu:getItems()[self:getMenuSelectedItemIndex(menu)]
end

function getCurrentMenuSelectedItemIndex(self)
	local menu = self:getCurrentMenu()
	return self:getMenuSelectedItemIndex(menu)
end

function getMenuSelectedItemIndex(self, menu)
	return menu:getSelectedIndex() or 1
end

function getCurrentMenu(self)
	local window = Framework.windowStack[1]

	-- find widget
	local widget = {}
	window:iterate(function(w)
		if oo.instanceof(w, Menu) then
			widget[#widget + 1] = w
		end
	end)

	return unpack(widget)
end
function getAllTracks(self, location, outputHandlerCallback)
	return self:jrmcRequest("?command=show-all&location=" .. location, callOutputHandlerCallback, menu, outputHandlerCallback)
end

--todo: This calback technique seems messy, revisit
function callOutputHandlerCallback(self, jrItems, menu, outputHandlerCallback)
	outputHandlerCallback(self.currentOutputHandler, jrItems)
end

function getContextWindow(self, text)
	local window = Window("contextWindow", text)
	window:setAutoHide(true)
	window:setTransparent(true)
	window._DEFAULT_SHOW_TRANSITION = transitionFadeInFast
	window._DEFAULT_HIDE_TRANSITION = Window.transitionNone

	return window
end

function showPlayPopup(self, origMenuItem)
	local window = self:getContextWindow(origMenuItem.text)
	--fallback to default handler
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)
	window:addListener(EVENT_KEY_PRESS,
			   function(evt)
				   if (evt:getKeycode() == KEY_PLAY) then
				        self.currentOutputHandler:play(origMenuItem)
   					    window:hide()
				        return EVENT_CONSUME
				   elseif (evt:getKeycode() == KEY_ADD) then
					    --self.currentOutputHandler:addPlayNow(origMenuItem)
   					    window:hide()
				    	return EVENT_CONSUME
				   end

				   return EVENT_UNUSED
			   end)

	local menu = SimpleMenu("menu",
				{
					{
						text = "Play (clears current list)",
						icon = Icon("keyPlay"),
						sound = "WINDOWHIDE",
						callback = function()
								   self.currentOutputHandler:play(origMenuItem)
								   window:hide()
							   end
					},
					{
						text = "Play (keep current list) -tbd",
						icon = Icon("keyAdd"),
						sound = "WINDOWHIDE",
						callback = function()
--								   self.currentOutputHandler:addPlayNow(origMenuItem)
								   window:hide()
							   end
					},
					{
						text = "Play (shuffled) -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
					--idea: if video offer play (from where last stopped or from beginning)
				})

	if (not origMenuItem.isFolder) then
		menu:addItem({
						text = "Rating",
						sound = "WINDOWHIDE",
						icon = Choice(
					       "choice",
					       { "unrated", "*", "**", "***", "****", "*****" },
					       function(obj, selectedIndex)
						       log:info(
								"Choice updated: ",
								tostring(selectedIndex),
								" - ",
								tostring(obj:getSelected()))

								origMenuItem.rating = getRating(selectedIndex);
								self:jrmcRequest("?command=rate&file=" .. origMenuItem.file .."&rating=" .. origMenuItem.rating, nil, menu)
					       end,
					       getRatingIndex(origMenuItem.rating)
			       		),
						callback = function()
								   window:hide()
							   end
					})
	end
	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function getRatingIndex(rating)
    if (rating == "unrated") then
        return 1
    else
        return rating + 1
    end
end

function getRating(ratingIndex)
    if (ratingIndex == "unrated") then
        return "unrated"
    else
        return ratingIndex - 1
    end
end

function showAddPopup(self, origMenuItem)
	local window = self:getContextWindow(origMenuItem.text)
	--fallback to default handler
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)
	window:addListener(EVENT_KEY_PRESS,
			   function(evt)
				   if (evt:getKeycode() == KEY_ADD) then
					   self.currentOutputHandler:addEnd(origMenuItem)
   					    window:hide()
				        return EVENT_CONSUME
				   elseif (evt:getKeycode() == KEY_PLAY) then
--				    	self.currentOutputHandler:addAsNextToPlay(origMenuItem)
   					    window:hide()
				    	return EVENT_CONSUME
				   end

				   return EVENT_UNUSED
			   end)
	local menu = SimpleMenu("menu",
				{
					{
						text = "Add (to end)",
						icon = Icon("keyAdd"),
						sound = "WINDOWHIDE",
						callback = function()
								   self.currentOutputHandler:addEnd(origMenuItem)
								   window:hide()
							   end
					},
					{
						text = "Add (as next to play) -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
					{
						text = "Add (shuffled) -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
					{
						text = "Add (shuffle all) -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
				})


	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end


function showHomePopup(self)
	local window = self:getContextWindow("Player Options")
	--fallback to default handler
	window:addListener(EVENT_KEY_PRESS | EVENT_KEY_DOWN | EVENT_KEY_HOLD, function(event) return keyEvent(self, event) end)
	window:addListener(EVENT_KEY_PRESS,
		   function(evt)
			   if (evt:getKeycode() == KEY_HOME) then
				   	self:nowPlayingWindow()
					window:hide()
					return EVENT_CONSUME
			   end

			   return EVENT_UNUSED
		   end)
	local menu = SimpleMenu("menu",
				{
					{
						text = "Now Playing",
						icon = Icon("keyHome"),
						sound = "WINDOWHIDE",
						callback = function()
									self:nowPlayingWindow()
								   	window:hide()
							   end
					},
					{
						text = "Reshuffle - tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
								   self.currentOutputHandler:toggleShuffle()
							   end
					},
					{
						text = "Repeat On/Off -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
								   self.currentOutputHandler:toggleRepeat()
							   end
					},
					{
						text = "Reshuffle Remaining -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
					{
						text = "Exit the River -tbd",
						sound = "WINDOWHIDE",
						callback = function()
								   window:hide()
							   end
					},
				})


	window:addWidget(menu)

	self:tieAndShowWindow(window)
	return window
end

function transitionFadeInFast(oldWindow, newWindow)
	local frames = FRAME_RATE / 2
	local scale = 255 / frames

	local bgImage = Framework:getBackground()

	local sw, sh = Framework:getScreenSize()
	local srf = Surface:newRGB(sw, sh)

	-- assume old window is not updating
	bgImage:blit(srf, 0, 0, sw, sh)
	oldWindow:draw(srf, LAYER_ALL)

	return function(widget, surface)
			local x = frames * scale

			newWindow:draw(surface, LAYER_ALL)
			srf:blitAlpha(surface, 0, 0, x)

			frames = frames - 10
			if frames <= 0 then
				Framework:_killTransition()
			end
		end
end

function skin(self, s)
	local screenWidth, screenHeight = Framework:getScreenSize()

	s.keyAdd = {
		img = Surface:loadImage("applets/DefaultSkin/images/selection_add.png")
	}
	s.keyPlay = {
		img = Surface:loadImage("applets/DefaultSkin/images/selection_play.png")
	}
	s.keyHome = {
		img = Surface:loadImage("applets/DefaultSkin/images/mini_home.png")
	}
	s.nowPlaying = {
		img = Surface:loadImage("applets/DefaultSkin/images/mini_nowplaying.png")
	}
	s.contextWindow = {}
	s.contextWindow.w = screenWidth
	s.contextWindow.h = screenHeight
	s.contextWindow.bgImg = Tile:fillColor(0x007573ef)

end

--[[
Notes
To enable Go To Tools->Options->Startup-> Check Run Remote Server on port x
Example JRMC web response ("Remote Server")
<html><style>
	@import 'styles.css';
</style><body>
<title>Media Library|Video|Theater</title>[<a href="/">Home</a>] [<a href="view?location=Media+Library">Media Library</a>] [<a href="view?location=Media+Library%7CVideo">Video</a>] [Theater]<br><br>
<table><thead><tr><td>Name</td><td>Actions</td></thead>
<tr valign=top><td><a href="view?location=Media+Library%7CVideo%7CTheater%7CDVDS">DVDS</a></td><td>[<a href="play?location=Media+Library%7CVideo%7CTheater%7CDVDS">play</a>] [<a href="play?add=1&location=Media+Library%7CVideo%7CTheater%7CDVDS">insert</a>] [<a href="play?add=2&location=Media+Library%7CVideo%7CTheater%7CDVDS">add</a>]</td></tr>

<tr valign=top><td><a href="view?location=Media+Library%7CVideo%7CTheater%7CShows">Shows</a></td><td>[<a href="play?location=Media+Library%7CVideo%7CTheater%7CShows">play</a>] [<a href="play?add=1&location=Media+Library%7CVideo%7CTheater%7CShows">insert</a>] [<a href="play?add=2&location=Media+Library%7CVideo%7CTheater%7CShows">add</a>]</td></tr>
</table>
<br><br>(C) 2003-2006, J. River Inc<br>Remote Server v1.6, J. River, Daniel Ellison, and Benjamin Sweet<br></body></html>

TODO:
- add bumps for things that do nothing
- solve URL decoding non-standard character (%E9 'lower e acute accent', for instance, doesn't work with string.decode (on windows squeezeplay or on actual controller)
- Screensavers take keybinding control away so pause/etc go against Squeeze not JRMC (not sure how to handle)
- how to best handle Home button (in "JRMC/Home Theater" mode, maybe want home to show JRMC playlist, though stealing home key binding is a bit evil since users want to get back out
- how to better handle key handling in general (for instance pause should paus JR no matter what window is active if we are in a JR output tree)

DONE:
- don't show '>' if menu item doesn't drill in - DONE

-- things requiring a custom JR web server
-rating - done
-cover art
-other add/play choices
-video handling (skip ahead/back, start from beginning)
-track info
-Locate Artist, Album, [Any Field] functionality

Bugs:
- Rating a song might invalidate a parent menu choice (pretty rare - when in a view scheme that has rating in it)

Ideas:
Party mode: "Play (clear list)" disabled, plus max of say 2 songs added epr click even if selecting an artist/album/genre/folder
--]]

--[[

=head1 LICENSE

Copyright 2007 Tom Wadzinski. All Rights Reserved.

=cut
--]]
